package com.palrevo.search;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.io.Charsets;
import org.apache.commons.io.FileUtils;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.ar.ArabicAnalyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.StringField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopScoreDocCollector;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import org.w3c.tidy.Tidy;

/**
 * 
 * @author AAsha
 *
 */
public class LuceneSearchHandler {

	private static StandardAnalyzer standardAnalyzer = new StandardAnalyzer(Version.LUCENE_45);
	private static ArabicAnalyzer arabicAnalyzer = new ArabicAnalyzer(Version.LUCENE_45);
	private static String REGEX = "[\\.]*<ui:define name=\"title\">";
	
	public enum SearchAnalyzer{
		ENGLISH(standardAnalyzer),ARABIC(arabicAnalyzer);
		
		private Analyzer analyzer;
		SearchAnalyzer(Analyzer analyzer){
			this.analyzer = analyzer;
		}
		
		public Analyzer getAnalyzer(){
			return this.analyzer;
		}
	}

	private IndexWriter writer;

	private ArrayList<File> queue = new ArrayList<File>();
	
	private String indexFilesDir = "";
	
	/**
	 * Constructor
	 * 
	 * @param indexDir
	 *            the name of the folder in which the index should be created
	 * @throws java.io.IOException
	 *             when exception creating index.
	 */
	public LuceneSearchHandler(String indexDir) throws IOException {
		this.indexFilesDir = indexDir;
	}

	/**
	 * Indexes a file or directory
	 * 
	 * @param fileName
	 *            the name of a text file or a folder we wish to add to the
	 *            index
	 * @throws java.io.IOException
	 *             when exception
	 */
	private void indexFileOrDirectory(String filesToIndexDir, SearchAnalyzer analyzer) throws IOException {
		
		IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_45, analyzer.getAnalyzer());
		
		FSDirectory dir = FSDirectory.open(new File(indexFilesDir));
		writer = new IndexWriter(dir, config);
		

		// ===================================================
		// gets the list of files in a folder (if user has submitted
		// the name of a folder) or gets a single file name (is user
		// has submitted only the file name)
		// ===================================================

		addFiles(new File(filesToIndexDir));
		for (File f : queue) {
			try {

				writer.addDocument(this.getDocument(f));
				System.out.println("Added: " + f);

			} catch (Exception e) {
				System.out.println("Could not add: " + f);
				e.printStackTrace();
			}
		}

		queue.clear();
	}

	/**
	 * Add file or directory files to queue.
	 * 
	 * @param file
	 */
	private void addFiles(File file) {

		if (!file.exists()) {
			System.out.println(file + " does not exist.");
		}else if(file.getName().equals("template")){
			System.out.println(file + " file is banned !");
		}else if (file.isDirectory()) {
			for (File f : file.listFiles()) {
				addFiles(f);
			}
		} else {
			String filename = file.getName().toLowerCase();
			// ===================================================
			// Only index text files
			// ===================================================
			if (filename.endsWith(".xhtml")) {
				queue.add(file);
			} else {
				System.out.println("Skipped " + filename);
			}
		}
	}
	
	/**
	 * 
	 * @param indexLocation
	 * @param indexDirectory
	 * @throws Exception 
	 */
	public void doIndex(SearchAnalyzer analyzer, String htmlFilesDir) throws Exception {
		try {
			indexFileOrDirectory(htmlFilesDir, analyzer);
			clearIndexFiles();
			closeIndex();
		} catch (Exception ex) {
			ex.printStackTrace();
			throw ex;
		}
	}
	

	/**
	 * Close the index.
	 * 
	 * @throws java.io.IOException
	 *             when exception closing
	 */
	private void closeIndex() throws IOException {
		writer.close();
	}

	private Document getDocument(File file) throws Exception {

		Tidy tidy = new Tidy();
		tidy.setInputEncoding("UTF-8");
		tidy.setOutputEncoding("UTF-8");
		tidy.setQuiet(true);
		tidy.setShowWarnings(false);
		
		FileInputStream fis = null;
		Document doc = null;
		try{
			fis = new FileInputStream(file);
			
			org.w3c.dom.Document root = tidy.parseDOM(fis, null);
			Element rawDoc = root.getDocumentElement();
			String body = getBody(rawDoc);
			String title = getTitle(file);

			doc = new Document();

			if (body != null && !body.isEmpty() && title != null && !title.isEmpty()) {
				doc.add(new TextField("contents", body, Field.Store.YES));
				doc.add(new StringField("path", file.getPath(), Field.Store.YES));
				doc.add(new StringField("title", title, Field.Store.YES));
			}
			
		}finally {
			if(fis != null){
				fis.close();
			}
		}
		

		return doc;
	}

	private String getTitle(File file) throws IOException {
		if (file == null) {
			return null;
		}

		List<String> fileTextLines = FileUtils.readLines(file, Charsets.UTF_8.toString());
		
		String title = "";
		boolean saveNextLine = false;
		
		Pattern pattern = Pattern.compile(REGEX);
		Matcher matcher = null;
		
		for(String line:fileTextLines){
			matcher = pattern.matcher(line);
			if(matcher.find()){
				saveNextLine = true;
			}else if(saveNextLine){
				title = line.trim();
				return title;
			}
		}
		
		return title;
	}

	private String getBody(Element rawDoc) throws UnsupportedEncodingException {
		if (rawDoc == null) {
			return null;
		}

		String body = "";
		NodeList children = rawDoc.getElementsByTagName("body");
		if (children.getLength() > 0) {
			body = getText(children.item(0));
		}
		return body;
	}

	private String getText(Node node) throws UnsupportedEncodingException {
		NodeList children = node.getChildNodes();
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < children.getLength(); i++) {
			Node child = children.item(i);
			switch (child.getNodeType()) {
			case Node.ELEMENT_NODE:
				sb.append(getText(child));
				sb.append(" ");
				break;
			case Node.TEXT_NODE:
				sb.append(((Text) child).getData());
				break;
			}
		}
		return sb.toString();
	}
	
	

	/**
	 * 
	 * @param keyword
	 */
	public List<Document> doSearch(String keyword, SearchAnalyzer analyzer) {
		
		List<Document> result = null;
		
		try {

			IndexReader reader = DirectoryReader.open(FSDirectory
					.open(new File(indexFilesDir)));
			IndexSearcher searcher = new IndexSearcher(reader);
			TopScoreDocCollector collector = TopScoreDocCollector.create(5,
					true);
			
			Query q = new QueryParser(Version.LUCENE_45, "contents", analyzer.getAnalyzer()).parse(keyword);
			searcher.search(q, collector);
			ScoreDoc[] hits = collector.topDocs().scoreDocs;

			// display results
			System.out.println("Found " + hits.length + " hits.");
			result = new ArrayList<Document>();
			for (int i = 0; i < hits.length; ++i) {
				int docId = hits[i].doc;
				Document d = searcher.doc(docId);
				result.add(d);
				System.out.println((i + 1) + ". " + d.get("path") + " score="
						+ hits[i].score);
			}

		} catch (Exception e) {
			System.out.println("Error searching " + keyword + " : "
					+ e.getMessage());
		}
		
		return result;
	}
	
	/**
	 * 
	 * @param indexFilesDir
	 * @throws IOException
	 */
	public void clearIndexFiles(){
		
		File dir = new File(indexFilesDir);
		
		if(dir.exists() && dir.isDirectory()){
			for (File f : dir.listFiles()) {
				try {
					FileUtils.forceDelete(f);
				} catch (IOException e) {
					System.out.println("==== Exception Deleting File >> "+f.getName()+" =====");
				}
			}
		}
		
	}

}
